package com.karina.visualization.chart.component;

import java.util.ArrayList;
import java.util.List;

import com.karina.visualization.chart.client.Context;
import com.karina.visualization.chart.client.DisplayObject;
import com.karina.visualization.chart.client.Graphics;
import com.karina.visualization.chart.model.ModelEvent;
import com.karina.visualization.chart.model.ModelEventHandler;
import com.karina.visualization.chart.model.ShapeModel;
import com.karina.visualization.chart.skins.Skin;

/**
 * 
 * The chartbase class which is the base class of all other chart, this chart is
 * able to help other chart
 * 
 * @author kenny kenny.zlee@gmail.com
 * 
 */
abstract public class ChartObject<Model extends ShapeModel<?>, Renderer extends Skin<Model>>
		extends UpdateableObject<Model> implements ModelEventHandler {

	private Model model_;

	private Renderer skin_;

	private Context context;

	private DisplayObject<?> parent;

	private Model old_model = null;

	protected Boolean enableEffect = false;

	protected final int FRAME_DURATION = TweenEffectPlayer.DEFAULT_FRAME;

	protected final List<DisplayObject<?>> children = new ArrayList<DisplayObject<?>>();

	private TweenEffectPlayer tween;

	public ChartObject(Model model, Renderer skin) {
		this.model_ = model;

		if (model != null) {
			model.addModelHandler(ModelEvent.model_change_type, this);
		}
		// skin.setData(model);
		this.skin_ = skin;
	}

	@Override
	public void setParent(DisplayObject<?> parent) {
		if (parent == this.parent)
			return;
		this.parent = parent;

		invalidteChartObjectModel();
	}

	private String extractSelectorName() {

		String name = getSelectorName();

		DisplayObject<?> p = getParent();
		while (p != null) {
			name = "." + p.getSelectorName() + "." + name;
			p = p.getParent();
		}
		return name;

	}

	private DisplayObject<?> getRoot() {

		DisplayObject<?> parent = this;
		DisplayObject<?> root;
		do {
			root = parent;
			parent = parent.getParent();
		} while (parent != null);

		return root;
	}

	@Override
	public DisplayObject<?> getParent() {
		return parent;
	}

	@Override
	public void addChild(DisplayObject<?> child) {
		child.setContext(context);
		child.setParent(this);
		children.add(child);
		child.setVsible(!isInvisible());
		invaliadteChartObjectProperties();
	}

	@Override
	public void removeChild(DisplayObject<?> child) {
		if (children.contains(child)) {
			child.setParent(null);
			children.remove(child);
			caculateChartObjectModelProperties();
		}
	}

	public List<DisplayObject<?>> getChildren() {
		return children;
	}

	@Override
	public void setContext(Context ctx) {
		context = ctx;
	}

	public Context getContext() {
		return context;
	}

	protected Renderer getSkin() {
		return skin_;
	}

	protected void setSkin(Renderer skin) {
		skin_ = skin;
	}

	@Override
	public void setData(Model data) {
		if (model_ != data) {

			old_model = model_;

			model_ = data;
			invalidteChartObjectModel();
			// skin_.setData(data);
		}
	}

	public void enableEffect(boolean enable) {
		enableEffect = enable;
	}

	public void play(int duration) {
		if (enableEffect && old_model != null) {
			tween = new TweenEffectPlayer(this, old_model, model_);
			tween.play(duration / FRAME_DURATION);
		}
	}

	protected boolean next() {
		if (enableEffect && old_model != null) {
			return tween.nextStep();
		}
		return false;
	}

	@Override
	public Model getData() {
		return model_;
	}

	@Override
	public void modelChange(ModelEvent event) {
		((UpdateableObject<?>) getRoot()).invalidteChartObjectModel();
	}

	@Override
	public void setVsible(boolean visible) {
		super.setVsible(visible);
		for (DisplayObject<?> child : children) {
			if (child.isInvisible() == visible) {
				child.setVsible(visible);
			}
		}
	}

	@Override
	void update() {
		super.update();
		draw(getData());
	}

	private void draw(Model data) {
		skin_.setSelectorName(extractSelectorName());
		if (invalidateProperties || invalidateModelProperties) {
			Graphics g = context.getGraphics();
			skin_.setData(data);
			skin_.cleanup(g);
			skin_.draw(g);
			invalidateModelProperties = false;
			invalidateProperties = false;
		}
	}

	/**
	 * set the arpha of the class
	 * 
	 * @param arapha
	 */
	@Override
	protected void setArapha(double arpha) {
		getSkin().setArapha(arpha);
	}

	@Override
	protected void caculateChartObjectProperties() {
		// do nothing;
	}

	@SuppressWarnings("rawtypes")
	@Override
	protected void caculateChartObjectModelProperties() {
		DisplayObject<?> parent = getParent();
		if (parent != null) {
			ShapeModel parentModel = (ShapeModel) parent.getData();
			getData().setWidth(parentModel.getWidth());
			getData().setHeight(parentModel.getHeight());
			getData().setX(parentModel.getX());
			getData().setY(parentModel.getY());
		}
	}

}